package com.ease.util.gc {
import com.ease.util.ProfilingManager;
import com.ease.util.configuration.ConfigurationUtil;
import com.ease.util.logging.LogManager;

import flash.events.TimerEvent;
import flash.net.LocalConnection;
import flash.system.System;
import flash.utils.Timer;
import flash.utils.getTimer;

import mx.core.FlexGlobals;
import mx.core.UIComponent;
import mx.logging.ILogger;

public class GarbageCollector {
   public static const DEFAULT_INTERVAL:int = 30000;
   private static const MIN_TIME_BETWEEN_2_GC:int = 5000;
   private static var _logger:ILogger = LogManager.getLogger(GarbageCollector);

   //---------------------------------------------------------------------------
   // Class Properties

   //-------------------------------------------------------
   // instance
   private static var _instance:GarbageCollector;

   public static function get instance():GarbageCollector {
      if (!_instance) {
         _instance = new GarbageCollector(PrivateConstructorToken);
      }
      return _instance;
   }

   public function GarbageCollector(token:Class) {
      if (token != PrivateConstructorToken) {
         throw new Error("GarbageCollector cannot be instantiated directly.");
      }
      if (this.periodicGCEnabled) {
         _timer = new Timer(DEFAULT_INTERVAL);
         _timer.addEventListener(TimerEvent.TIMER, handleTimer);
      }

      _gcPendingTimer = new Timer(0, 1);
      _gcPendingTimer.addEventListener(TimerEvent.TIMER, pendingGCTimerHandler);
   }

   public function initialize(interval:int = DEFAULT_INTERVAL):void {
      this.interval = interval;
   }


   //---------------------------------------------------------------------------
   // Instance Variables

   /**
    * A boolean value that specifies if it is a forced GC (somewhere from the application)
    * or called from the end of the timer cycle.
    */
   private var _gcPending:Boolean = false;
   private var _timer:Timer;
   private var _gcPendingTimer:Timer;
   private var _interval:int = 0;
   private var _lastGCTime:int;
   private var _pendingExtern:Boolean = false;

   /**
    * The system's total memory after the last garbage collection.
    */
   [Bindable]
   public var totalMemory:Number = System.totalMemory;

   //---------------------------------------------------------------------------
   // Instance Properties

   /**
    * Returns true if the Periodic Garbage Collector is enabled
    */
   public function get periodicGCEnabled():Boolean {
      return ConfigurationUtil.getValueAsBoolean("periodicGC", false);
   }

   //-------------------------------------------------------
   // interval

   /**
    * At what intervals the garbage collection process should run.
    */
   public function get interval():int {
      return _interval;
   }
   public function set interval(value:int):void {
      if (_interval != value) {
         _interval = value;
         if (_timer == null) {
            return;
         }

         _timer.stop();
         _timer.delay = _interval;
         if (_interval > 0) {
            _timer.start();
         }
      }
   }

   //---------------------------------------------------------------------------
   // Instance Methods

   /**
    * Forces the garbage collection process.
    */
   public function gc():void {
      doRealGC(true);
   }

   /**
    * Force a GC on the next flash cycle.
    */
   internal function gcLater():void {
      var app:UIComponent = UIComponent(FlexGlobals.topLevelApplication);
      app.callLater(gcInternal);
   }

   private function gcInternal():void {
      doRealGC(true);
   }

   private function doPendingGC(pendingTime:int, externalRequest:Boolean):void {
      if (!_gcPending) {
         _gcPending = true;
         _pendingExtern = externalRequest;
         _gcPendingTimer.delay = pendingTime;
         _gcPendingTimer.start();
      }
   }

   private function doRealGC(externalRequest:Boolean):void {
      //It is not possible to manually invoke gc in Flash.
      //A hack to invoke the gc was found and published in the year 2006. The hack was ...
      //LocalConnection().connect("foo");
      //LocalConnection().connect("foo");
      //call LocalConnection.connect twice. This throws an error ... #2082 Already connected (on the 2nd one).
      //LocalConnection is Flash package class hence the source code is not available
      //but internally it must be invoking the garbage collector when connect fails.
      //We modified it a little bit by using only 1 LocalConnection.
      //The 1st call to this method will not invoke the gc.
      //The 2nd call (and all subsequent calls) to this method will invoke the gc.
      //if the user has a debugger version of Flash plugin then we invioke the System.gc()
      //which is available in the debugger version only.

      //we do gc if ...
      //allowManualGC=true : If developers want to manually invoke gc.
      //AND last gc was invoked atleast 10 seconds before

      //if the gc() is invoked in short intervals then do not gc, make it pending.
      var diff:int = getTimer() - _lastGCTime;
      if (diff < MIN_TIME_BETWEEN_2_GC) {
         doPendingGC(MIN_TIME_BETWEEN_2_GC - diff, externalRequest);
         return;
      }

      if (externalRequest) {
         ProfilingManager.instance.cumulativeOperationStarted(_logger, "gc");
      }
      _logger.info("Garbage Collection Invoked");
      try {
        //hack urls: http://www.gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
         new LocalConnection().connect("foo");
         new LocalConnection().connect("foo");
      } catch (e:Error) {
         //do not handle error #2082 because the above code will fail and throw an error #2082
         //every time gc happens because the connect fails.
         if (!(e is ArgumentError) || (e as ArgumentError).errorID != 2082) {
            trace("Unexpected error during garbage collection: " + e.message);
         }
      }
      if (externalRequest) {
         ProfilingManager.instance.cumulativeOperationEnded(_logger, "gc");
         restartTimer();
      }
      this.totalMemory = System.totalMemory;
      _lastGCTime = getTimer();
      _gcPending = false;
   }

   /**
    * Restart the periodic timer.
    */
   private function restartTimer():void {
      if (_timer == null) {
         return;
      }
      if (_interval > 0) {
         _timer.reset();
         _timer.start();
      }
   }

   private function handleTimer(evt:TimerEvent):void {
      doRealGC(false);
   }

   private function pendingGCTimerHandler(evt:TimerEvent):void {
      if (_gcPending) {
         doRealGC(_pendingExtern);
      }
   }
}

}

// http://livedocs.adobe.com/flex/3/html/03_Language_and_Syntax_05.html#151506
class PrivateConstructorToken {}